package org.weso.acota.dao.ontology;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.weso.acota.exceptions.AcotaModelException;
import org.weso.acota.loader.JenaOWLModelWrapper;
import org.weso.acota.loader.resources.FilesResourceLoader;
import org.weso.acota.loader.resources.ResourceLoader;
import org.weso.acota.loader.resources.URLFilesResourceLoader;
import org.weso.acota.to.ModelTO;
import org.weso.acota.to.OntologyTO;
import org.weso.acota.to.ProviderTO;
import org.weso.acota.to.ResourceTO;
import org.weso.acota.to.TagTO;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.ontology.Ontology;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;
import com.hp.hpl.jena.vocabulary.DC;
import com.hp.hpl.jena.vocabulary.OWL;

public class OntologyFacade{


	private OntModel model;
	private OntModel defaultModel; //It is static only in construction
	
	public OntologyFacade(){
		this.defaultModel = createDefaultModel();
		this.model = ModelFactory.createOntologyModel();
		updateModel();
	}

	private void updateModel() {
		this.model.add(this.defaultModel);
	}

	private OntModel createDefaultModel() {
		ResourceLoader loader = new FilesResourceLoader(new String[]{
				"ontologies/doap.rdf",
				"ontologies/foaf.rdf",
				//"ontologies/sioc.rdf",				
				"ontologies/common-tag.rdf",
				"ontologies/org.rdf"
				
		});
		JenaOWLModelWrapper wrapper = new JenaOWLModelWrapper(loader);
		return (OntModel)wrapper.getModel();
	}
	
	public OntologyFacade(OntologyDAO dao){
		this.defaultModel = createDefaultModel();
		this.model = (OntModel) dao.getModel().getModel();
		updateModel();
	}

	
	//Method to add: users, organizations and any sort of RDF resource
	public boolean loadResource(ResourceTO resource){
		ResourceLoader loader = new URLFilesResourceLoader(new String[]{resource.getUri()});
		JenaOWLModelWrapper wrapper = new JenaOWLModelWrapper(loader);
		this.model.add((OntModel)wrapper.getModel());
		return Boolean.TRUE;
	}
	
	
	
	//Model management
	public boolean addOntology(OntologyTO ontology){
		ResourceLoader loader = new URLFilesResourceLoader(new String[]{ontology.getUri()});
		JenaOWLModelWrapper wrapper = new JenaOWLModelWrapper(loader,ontology.getFormat());
		this.model.add((OntModel)wrapper.getModel());
		return Boolean.TRUE;
	}
	
	public List<OntologyTO> listOntologies(){
		List<OntologyTO> ontologies = new LinkedList<OntologyTO>();		
		ExtendedIterator<Ontology> it = this.model.listOntologies();
		while(it.hasNext()){
			Ontology onto = it.next();
			OntologyTO ontology = new OntologyTO();
			ontology.setUri(onto.getURI());
			ontology.setLabel(onto.getLabel("en"));
			ontology.setComment(onto.getComment("en"));
			ontologies.add(ontology);
		}
		return ontologies;
	}

	public OntologyTO getOntology(OntologyTO ontology){
		try {
			Ontology onto = this.model.getOntology(ontology.getUri());
			StringWriter writer = new StringWriter();
			onto.getOntModel().write(writer);
			writer.close();
			OntologyTO result = new OntologyTO();
			result.setUri(onto.getURI());
			result.setContent(writer.toString());
			return result;
		} catch (IOException e) {
			throw new AcotaModelException(e);
		}	
	}

	public ModelTO getModel(){
		StringWriter writer = new StringWriter();
		this.model.write(writer);
		ModelTO result = new ModelTO();
		result.setContent(writer.toString());
		return result;
	}

	public boolean newModel(){
		this.model = null;
		this.model = ModelFactory.createOntologyModel();
		updateModel();
		return Boolean.TRUE;
	}
	

	public boolean newModel(OntologyTO ontology){
		ResourceLoader loader = new URLFilesResourceLoader(new String[]{ontology.getUri()});
		JenaOWLModelWrapper wrapper = new JenaOWLModelWrapper(loader,ontology.getFormat());
		this.model = null;
		this.model = (OntModel) wrapper.getModel();
		updateModel();
		return Boolean.TRUE;
	}
	//Listing methods

	public List<ResourceTO> listClasses(){
		return createListOfResources(this.model.listClasses());
	}

	public List<ResourceTO> listProperties(){
//		this.model.listAnnotationProperties();
//		this.model.listDatatypeProperties();
//		this.model.listObjectProperties();		
		return createListOfResources(this.model.listAllOntProperties()); //FIXME: only ont properties
	}

	public List<ResourceTO> listInstances(){
		return createListOfResources(this.model.listIndividuals());
	}
	
	public List<ResourceTO> listInstancesOf(ResourceTO clazz){
		return createListOfResources(this.model.getOntClass(clazz.getUri()).listInstances(true));
	}

	public List<ResourceTO> listPropertiesOf(ResourceTO clazz){
		return createListOfResources(this.model.getOntClass(clazz.getUri()).listDeclaredProperties(true));
	}

	public boolean addTag(TagTO tag,ResourceTO resource){
		//http://commontag.org/ns#tagged
		//http://commontag.org/ns#means
		//http://commontag.org/ns#label
		//1-Create instance of tag class
		String tagUriClass = "http://commontag.org/ns#Tag";
		int id = tag.getLabel().hashCode()+tag.getProvider().getUri().hashCode();
		String uri = "http://purl.org/weso/ontologies/feedback/"+id;		
		Individual newInstance = 
			this.model.createIndividual(uri, this.model.getOntClass(tagUriClass));
//		newInstance.addProperty(this.model.getProperty("http://commontag.org/ns#means"), 
//				"");
		newInstance.addLabel(tag.getLabel(), "en");
		newInstance.addComment("Created new tag "+tag.getLabel(), "en");
		newInstance.addProperty(this.model.getProperty("http://commontag.org/ns#label"), 
				tag.getLabel());
		newInstance.addProperty(this.model.getProperty("http://commontag.org/ns#tagged"), 
				this.model.getOntResource(resource.getUri()));
		newInstance.addLiteral(DC.identifier, id);
		newInstance.addLiteral(DC.source, tag.getProvider().getUri());
		Date d = Calendar.getInstance().getTime();
		newInstance.addLiteral(DC.date, d.getTime());
		return Boolean.TRUE;
	}
	private TagTO createTagTO(Individual tagInstance){//FIXME: SPARQL
		String taggedUri = tagInstance.getProperty(this.model.getProperty("http://commontag.org/ns#tagged")
		).getObject().asResource().getURI();
		TagTO tag = new TagTO();		
		tag.setId(tagInstance.getProperty(DC.identifier).getString());
		String label = tagInstance.getProperty(
				this.model.getProperty("http://commontag.org/ns#label")).getString();
		tag.setLabel(label);
		ProviderTO provider = new ProviderTO();
		provider.setUri(tagInstance.getProperty(DC.source).getString());
		tag.setProvider(provider);		
		ResourceTO tagged = createResourceTO(this.model.getOntResource(taggedUri));
		tag.setTagged(tagged);
		tag.setTaggingDate(tagInstance.getProperty(DC.date).getString());
		tag.setUri(tagInstance.getURI());				
		tag.setLabel(label);
		return tag;
	}
	public List<TagTO> listTagsOf(ResourceTO resource){ //FIXME: SPARQL
		String tagUriClass = "http://commontag.org/ns#Tag";
		ExtendedIterator<Individual> tagInstances = 
			this.model.listIndividuals(this.model.getOntResource(tagUriClass));
		List<TagTO> tags = new LinkedList<TagTO>();
		while(tagInstances.hasNext()){
			Individual tagInstance = tagInstances.next();
			if(tagUriClass.equals(tagInstance.getRDFType().getURI())){
				String taggedUri = tagInstance.getProperty(
						this.model.getProperty("http://commontag.org/ns#tagged")
				).getObject().asResource().getURI();
				if(taggedUri.equals(resource.getUri())){
					tags.add(createTagTO(tagInstance) );
				}			
			}
		}
		return tags;
	}

	public List<TagTO> listTags(){
		String tagUriClass = "http://commontag.org/ns#Tag";
		ExtendedIterator<Individual> tagInstances = 
			this.model.listIndividuals(this.model.getOntResource(tagUriClass));
		List<TagTO> tags = new LinkedList<TagTO>();
		while(tagInstances.hasNext()){
			Individual tagInstance = tagInstances.next();
			if(tagUriClass.equals(tagInstance.getRDFType().getURI())){
				tags.add(createTagTO(tagInstance)); //FIXME: SPARQL
			}
		}		
		return tags;
	}
	
	public boolean addClass(ResourceTO clazz, ResourceTO parent){
		OntClass newClass = this.model.createClass(clazz.getUri());
		newClass.setLabel(clazz.getLabel(), "en");
		newClass.setComment(clazz.getDescription(), "en");
		newClass.addSuperClass(this.model.getOntClass(parent.getUri()));
		return Boolean.TRUE;
	}
	

	public boolean addInstance(ResourceTO instance, ResourceTO clazz){
		Individual newInstance = 
			this.model.createIndividual(instance.getUri(), this.model.getOntClass(clazz.getUri()));
		newInstance.setLabel(clazz.getLabel(), "en");
		newInstance.setComment(clazz.getDescription(), "en");
		return Boolean.TRUE;
	}
	
	

	public boolean addProperty(ResourceTO property, ResourceTO resource1, ResourceTO resource2){
		OntResource r1 = this.model.getOntResource(resource1.getUri());
		OntResource r2 = this.model.getOntResource(resource2.getUri());
		//FIXME: check if exists
		OntProperty prop = this.model.getOntProperty(property.getUri());
		if(prop == null){
			prop = this.model.createOntProperty(property.getUri());
		}
		this.model.add(r1, prop, r2);
		return Boolean.TRUE;
	}
	
	
	
	public boolean createProperty(ResourceTO property){
		OntProperty prop = this.model.getOntProperty(property.getUri());
		if(prop == null){
			prop = this.model.createOntProperty(property.getUri());
		}
		return Boolean.TRUE;
	}
	

	public ResourceTO getClass(ResourceTO clazz){
		return createResourceTO(this.model.getOntResource(clazz.getUri()));
	}
	

	public boolean removeClass(ResourceTO clazz){
		//this.model.getOntClass(clazz.getUri()).getOntModel().write(System.out);
		//this.model.remove(this.model.getOntClass(clazz.getUri()).getOntModel());
		return Boolean.FALSE;
	}
	

	public boolean removeInstance(ResourceTO clazz){
		return Boolean.FALSE;
	}
	

	public boolean removeProperty(ResourceTO clazz){
		return Boolean.FALSE;
	}
	

	public boolean removeTag(ResourceTO clazz){
		return Boolean.FALSE;
	}

	public ResourceTO getProperty(ResourceTO property){
		return createResourceTO(this.model.getOntResource(property.getUri()));
	}
	

	public ResourceTO getInstance(ResourceTO instance){
		return createResourceTO(this.model.getOntResource(instance.getUri()));
	}
	//Helper methods
	private static List<ResourceTO> createListOfResources(ExtendedIterator<? extends OntResource> it){
		List<ResourceTO> resources = new LinkedList<ResourceTO>();		
		while(it.hasNext()){
			resources.add(createResourceTO(it.next()));
		}
		return resources;
	}
	private static ResourceTO createResourceTO(OntResource ontResource){
		ResourceTO resource = new ResourceTO();
		resource.setUri(OWL.Nothing.getURI());
		if(ontResource != null){		
			resource.setUri(ontResource.getURI());
			resource.setLabel(ontResource.getLabel("en"));
			resource.setDescription(ontResource.getComment("en"));
			resource.setType(ontResource.getClass().getSimpleName());
		}
		return resource;
	}
}
